松本行弘的程序世界 4 设计模式

设计模式(1)

设计模式的价值和意义

Gamma他们并没有发现新的模式,总结出来的23种设计模式也是软件开发中早就存在并反复使用的模式,因此并不能说是Gamma他们的首创。但即使是这样,设计模式有了名字,人们就可以认识到它的存在,并对之进行讨论。这种不能用语言表达的知识称为内隐知识。给这些在软件中经常反复出现的模式命名,使得这些本来只有经验丰富的程序员才能认识到的软件设计模式能被广泛认识和讨论。

设计模式是程序抽象化的延伸

一旦有了设计模式,只要把过去优秀的人们考虑出来的模式拿来应用一下。

Ruby中的设计模式

Singleton模式

单件模式。用来保证某个类的实例只有一个。
Ruby实现Singleton模式的方法有几个

  1. 使用singleton库的方法
    Ruby以库的形式实现了Singleton模式。使用Singleton库,在任意类中include Singleton模块,那个类就变成Singleton模式的对象。
  2. 使用类或模块
    C++和java是不能把类作为对象使用的,与之不同的是,smalltalk或Ruby能把类也作为对象来处理。因此,在类或模块中定义一个方法就可以实现Singleton模式。
  3. 把一般的对象作为Singleton来使用
    为了把一个类的对象限制为只有一个,并不一定要对对象的一般生产方法进行限制。可以生成一个一般的对象,然后不要在生产更多个对象了。
  4. 使用对象和特异方法
    Ruby可以在对象生成以后再增加新的方法。

Proxy模式

Proxy(代理)模式是为某个对象提供代理对象的模式。
在不知道是否真正需要一个生成代价很大的对象时可能造成很大浪费,但不生成又什么都做不了。

Iterator模式

Iterator(迭代器)模式提供按顺序访问集合对象中各元素的方法。即使不知道对象的内部构造,也可以按顺序访问其中的每个元素。
Iterator模式是为集合对象另外准备用来控制循环处理的对象,就像C++或java一样,我们称这个循环控制对象为Iyerator,也称为游标。

外部与内部,哪一个更好

它们都有方便的一面,也都有不方便的另一面。

内部迭代器的缺陷

内部迭代器不能同时进行多个循环,也就无法实现按顺序比较两个集合元素的处理。

外部迭代器的缺陷

外部迭代器的缺陷在于迭代器对象需要引用集合对象的内部信息,为了按顺序访问集合对象的各个元素,迭代器需要访问集合的内部构造,破坏了隐藏集合内部构造的封装性原则。

设计模式(2)

模式与动态语言的关系

《设计模式》一书中介绍了23个设计模式。这些设计模式可分为3大类:(1)有关生成的模式(5个),有关构造的模式(7个)以及有关行为的模式(11个)。Singleton为(1),Proxy为(2),Iterayor为(3)。

重复使用既存对象的Protoype模式

Protoype(原型)模式明确一个实例作为要生成对象的种类原型,通过复制该实例来生成新的对象。
在需要新种类对象时,首先复制一个既存的对象,给复制的对象直接增加方法或实例变量等功能,生成最初的第一个新种类对象。最初一个也并不特别,只是偶尔被用来复制而已。
相对于类模式编程,原型模式的编程构成元素比较少,具有简单实现面向对象功能设计的倾向,JavaScript的面向对象就是原型模式。io语言也是。

亲身体验IO语言

Ruby中的原型

基本上讲Ruby是类模式的语言,但也拥有支持原型模式编程功能。

  1. 复制对象的clone方法
  2. 给个别对象增加方法的特意方法功能
  3. 给个别对象增加一组功能的extend方法

静态语言中没有原型编程,因为不可能给复制的对象增加新方法。

编写抽象算法的Template Method模式

用Ruby来尝试Template Method

Ruby的类库中最大限度灵活运用Template Method模式的部分,应该是Enumerable模块和Comparable模块了。

动态语言与Template Method模式

Template Method模式的这种优秀性质与语言是不是静态没有关系。

避免高度依赖性的Observer模式

Observer(观察者)模式是当某个对象的状态发生变化时,依存于该状态的全部对象都自动得到通知,而且为了让它们都得到通知,定义了对象间一对多的依存关系。
这是控制类与类之间依存关系的一种模式。
高度依赖性会导致组成程序的零件过大,避免高度依赖性的Observer模式,构成观察者模式的有两个对象,一个称谓Observer(观察者)接受变更通知;另一个称为Subject(对象)或Observable(被观察者),发出变更通知。
被观察者让人得到被动的印象,在实际处理中,被观察者会发出通知“我已经变化了哦”。

Observable模块

Ruby中为实现Observer模式提供了名为observer的库。observer库提供observer模块。

Observer模式与动态语言

由于Ruby的动态性质,Observer库具有以下几方面的灵活性。

  1. 观察者类不必是特定类的子类。
  2. 观察者类不必实现特定的接口(本来在Ruby中也没有接口)
  3. 观察者类的更新方法名可以自由决定
  4. 观察者类更新方法的参数可以自由决定
  5. 被观察者类不必是特定类的子类
  6. 对被观察者类的要求,只是将Observable模块包括进来。
1
2
3
说到事件监听模式,很容易将它和观察者模式联系在一起。
实质上这两者完成同类型的工作。依个人理解,事件监听模式更像是观察者模式的进阶。
事件监听机制就是对观察者模式进行了进一步抽象,节省了代码量。

设计模式(3)

软件开发的悲剧

  1. 复杂性
  2. 变化性

软件的规模越大,各个部分之间的牵连越复杂,更改也就越难。
在软件开发过程中,需求变更几乎是不可避免的。

开放-封闭原则

对模块扩展必须开放,对修改必须封闭。
为了应对将来的需要,扩展必须是开放的,但是即使某一模块的内部结构改变了,对外接口也应当是不变的。简称OCP。

面向对象的情况

既要开放,又要封闭,看似互相矛盾,但是面向对象编程语言能够很彻底地消除这个矛盾。

非面向对象的情况

非面向对象则很难处理好。
面向对象的精髓在于对OCP的实践。至于把对象看做物体理解起来比较容易,能够建立现实世界的模型等,不过是锦上添花。

OCP与Template Method模式

虽说使用面向对象语言的功能,可以实现OCP,但是只是说有这种可能性,并不是说什么时候都能实现。当然,虽然使用了面向对象语言,却做成了一个糟糕的设计,这种情况也是屡见不鲜。
分类中很多设计模式之所以优秀是因为经得起OCP所要求的变化。
Template Method模式,是满足OCP的基本手段。其他的设计模式都是利用多个类的关联来实现的,而Template Method模式则仅仅使用了继承,基本上无非是实现了一个抽象类。

Observer模式

Observer模式是满足OCP的。
DRY也好,OCP也好,都不过是原则,根据具体情况,要做适当的选择,如果代码没有再利用的打算,也没有扩展功能的打算,也就没有必要生搬硬套设计模式。使用前有必要先做判断。

使用Strategy模式

Strategy(策略)模式是定义算法的集合,将各算法封装,使它们能够交换。利用Strategy模式,算法和利用这些算法的客户程序可以分别独立进行修改而不互相影响。
Strategy模式就是将容易变化的处理归纳为独立的对象,然后使它们能够互相交换,使用方法与将容易变化的处理交给子类的Template Method模式相类似。两个模式最大的区别在于,Strategy模式是独立的对象,能够动态交换处理逻辑。

Strategy模式与OCP

Strategy模式完全满足OCP。
世上很多设计模式,为了能应对将来可能的修改,都是按照OCP的要求来设计的。